﻿/* BEGIN_COMMON_COPYRIGHT_HEADER
 * (c)LGPL2+
 *
 * Copyright: 2011 Razor team
 *            2014 LXQt team
 * Authors:
 *   Alexander Sokoloff <sokoloff.a@gmail.com>
 *   Maciej Płaza <plaza.maciej@gmail.com>
 *   Kuzma Shapran <kuzma.shapran@gmail.com>
 *
 * Copyright: 2019 Tianjin KYLIN Information Technology Co., Ltd. *
 *
 * This program or library is free software; you can redistribute it
 * and/or modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA
 *
 * END_COMMON_COPYRIGHT_HEADER */

#include "ukuitaskgroup.h"
#include "ukuitaskbar.h"

#include <QDebug>
#include <QMimeData>
#include <QFocusEvent>
#include <QDragLeaveEvent>
#include <QStringBuilder>
#include <QMenu>
#include <KF5/KWindowSystem/KWindowSystem>
#include <functional>
#include <QProcess>

#include <QtX11Extras/QX11Info>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

#include <QLabel>
#include <QScrollBar>

#include <qmainwindow.h>
#include <QWidget>
#include <QDesktopWidget>
#include <QApplication>
#include "../panel/iukuipanelplugin.h"
#include <QSize>
#include <QScreen>
#include <XdgIcon>
#include <XdgDesktopFile>
#include <QMessageBox>
#include "../panel/customstyle.h"
#define UKUI_PANEL_SETTINGS "org.ukui.panel.settings"
#define PANELPOSITION       "panelposition"

#define UKUI_PANEL_DAEMON           "org.ukui.panel.daemon"
#define UKUI_PANEL_DAEMON_PATH      "/convert/desktopwid"
#define UKUI_PANEL_DAEMON_INTERFACE "org.ukui.panel.daemon"
#define UKUI_PANEL_DAEMON_METHOD    "WIDToDesktop"


#define WAYLAND_GROUP_HIDE     0
#define WAYLAND_GROUP_ACTIVATE 1
#define WAYLAND_GROUP_CLOSE    2
#define PREVIEW_WINDOW_WIDTH 420
#define PREVIEW_WINDOW_HEIGHT 264


UKUITaskGroup::UKUITaskGroup(QuickLaunchAction * act, IUKUIPanelPlugin * plugin, UKUITaskBar * parent)
    : UKUITaskButton(act, plugin, parent),
      m_plugin(plugin),
      m_act(act),
      m_parent(parent)
{
    m_statFlag = false;
    setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    setAcceptDrops(true);
    /*设置快速启动栏的按键不接受焦点*/
    setFocusPolicy(Qt::NoFocus);
    setAutoRaise(true);

    setDefaultAction(m_act);
    m_act->setParent(this);

    /*设置快速启动栏的菜单项*/
    const QByteArray id(UKUI_PANEL_SETTINGS);
    m_gsettings = new QGSettings(id);
    toDoModifyQuicklaunchMenuAction(true);
    connect(m_gsettings, &QGSettings::changed, this, [=] (const QString &key){
                if(key==PANELPOSITION){
                    toDoModifyQuicklaunchMenuAction(true);
                }
            });
    setContextMenuPolicy(Qt::CustomContextMenu);
    setIconSize(QSize(m_plugin->panel()->iconSize(), m_plugin->panel()->iconSize()));

    m_fileName=act->m_settingsMap["desktop"];
    this->setStyle(new CustomStyle());
    repaint();
}

UKUITaskGroup::UKUITaskGroup(const QString &groupName, const WindowId& window, UKUITaskBar *parent)
    : UKUITaskButton(groupName, window, parent, parent),
    m_groupName(groupName),
    m_popup(new UKUIGroupPopup(this)),
    m_preventPopup(false),
    m_singleButton(true),
    m_timer(new QTimer(this)),
    m_parent(parent)
{
    Q_ASSERT(parent);
    m_taskGroupStatus = NORMAL;

    initSdkDesktopFileName(window);

    initActionsInRightButtonMenu();
    refreshIconsGeometry();
    getThumbnailIfAvailable();

    connect(this, SIGNAL(clicked(bool)), this, SLOT(onClicked(bool)));
    connect(WindowManager::self(), &WindowManager::activeWindowChanged, this, &UKUITaskGroup::onActiveWindowChanged);
    connect(parent, &UKUITaskBar::refreshIconGeometry, this, &UKUITaskGroup::refreshIconsGeometry);
    connect(parent, &UKUITaskBar::buttonStyleRefreshed, this, &UKUITaskGroup::setToolButtonsStyle);
    connect(parent, &UKUITaskBar::showOnlySettingChanged, this, &UKUITaskGroup::refreshSdkVisibility);
    m_timer->setTimerType(Qt::PreciseTimer);
    connect(m_timer, SIGNAL(timeout()), SLOT(timeout()));
}

UKUITaskGroup::~UKUITaskGroup()
{
}


void UKUITaskGroup::getThumbnailIfAvailable()
{

    QString filename = QString::fromLocal8Bit(PANEL_CONFIG_PATH);
    QSettings settings(filename, QSettings::IniFormat);
    settings.setIniCodec("UTF-8");
    settings.beginGroup("Thumbnail");
    m_isThumbnailAvailable = settings.value("thumbnailAvailable").toBool();
    settings.endGroup();
    settings.sync();

}

void UKUITaskGroup::initSdkDesktopFileName(const WindowId& window) {
    QDBusInterface iface(UKUI_PANEL_DAEMON,
                         UKUI_PANEL_DAEMON_PATH,
                         UKUI_PANEL_DAEMON_INTERFACE,
                         QDBusConnection::sessionBus());
    QDBusReply<QString> reply = iface.call(UKUI_PANEL_DAEMON_METHOD, window.toInt());
    QString processExeName = reply.value();
    if (!processExeName.isEmpty()) {
        m_fileName = processExeName;
    }
}

void UKUITaskGroup::initActionsInRightButtonMenu(){
    if (m_fileName.isEmpty()) return;
    const auto url=QUrl(m_fileName);
    QString fileName(url.isLocalFile() ? url.toLocalFile() : url.url());
    XdgDesktopFile xdg;
    if (xdg.load(fileName)) {
        m_act = new QuickLaunchAction(&xdg, this);
        setGroupIcon(m_act->getIconfromAction());
    }
}

void UKUITaskGroup::rightMenuCloseAction(QMenu *menu)
{
    QAction *mCloseAct = menu->addAction(QIcon::fromTheme("application-exit-symbolic"), tr("close"));
    connect(mCloseAct, SIGNAL(triggered()), this, SLOT(closeGroup()));
    connect(menu, &QMenu::aboutToHide, [this] {
                m_preventPopup = false;
            });
    plugin()->willShowWindow(menu);
}

void UKUITaskGroup::contextMenuEvent(QContextMenuEvent *event)
{
    setPopupVisible(false, true);
    m_preventPopup = true;
    QMenu * menu = new QMenu(tr("Group"), this);
    menu->setAttribute(Qt::WA_DeleteOnClose);
    //若应用卸载后主窗口仍为打开状态，则右键菜单只显示“退出”选项
    XdgDesktopFile xdg;
    if (!xdg.load(m_fileName)) {
        rightMenuCloseAction(menu);
        menu->setGeometry(plugin()->panel()->calculatePopupWindowPos(mapToGlobal(event->pos()), menu->sizeHint()));
        menu->show();
        return;
    }

    if (!m_fileName.isEmpty()) {
        menu->addAction(m_act);
        menu->addActions(m_act->addtitionalActions());
        if (m_existSameQckBtn) {
            QAction *m_deleteAct = menu->addAction(QIcon::fromTheme("ukui-unfixed-symbolic"), tr("delete from taskbar"));
            connect(m_deleteAct, SIGNAL(triggered()), this, SLOT(RemovefromTaskBar()));
        } else {
            QAction *mAddAct = menu->addAction(QIcon::fromTheme("ukui-fixed-symbolic"), tr("add to taskbar"));
            connect(mAddAct, SIGNAL(triggered()), this, SLOT(AddtoTaskBar()));
        }
    }
    menu->addSeparator();
    rightMenuCloseAction(menu);
    menu->setGeometry(plugin()->panel()->calculatePopupWindowPos(mapToGlobal(event->pos()), menu->sizeHint()));
    plugin()->willShowWindow(menu);
    menu->show();
}
void UKUITaskGroup::RemovefromTaskBar()
{
    emit WindowRemovefromTaskBar(m_fileName);
}
void UKUITaskGroup::AddtoTaskBar()
{
    emit WindowAddtoTaskBar(groupName());
}
/************************************************

 ************************************************/
void UKUITaskGroup::closeGroup()
{
     for (UKUISdkTaskButtonHash::const_iterator it = m_sdkButtonHash.begin(); it != m_sdkButtonHash.end(); ++it) {
        WindowManager::closeWindow(it.key());
    }
}

/************************************************

 ************************************************/
UKUITaskButton * UKUITaskGroup::addWindow(const WindowId& id)
{
    qint32 idnum = id.toInt();
    if (m_sdkButtonHash.contains(idnum)) {
        return m_sdkButtonHash.value(idnum);
    }
    UKUITaskButton *btn = new UKUITaskButton(m_groupName, id, parentTaskBar(), m_popup);
    btn->setTaskButtonVisible(false);
    m_sdkButtonHash.insert(idnum, btn);
    m_popup->addWindow(id,parentTaskBar());
//    connect(taskwidget, SIGNAL(clicked()), this, SLOT(onChildButtonClicked()));
//    connect(taskwidget, SIGNAL(windowMaximize()), this, SLOT(onChildButtonClicked()));
    refreshSdkVisibility();
    changeTaskButtonStyle();
    refreshIconsGeometry();
    update();

    return btn;
}

/*changeTaskButtonStyle in class UKUITaskGroup not class UKUITaskButton
 * because class UKUITaskButton can not get m_buttonHash.size
 */
void UKUITaskGroup::changeTaskButtonStyle()
{
        this->setStyle(new CustomStyle());
}

void UKUITaskGroup::onActiveWindowChanged(const WindowId& window)
{
    UKUITaskButton *button = m_sdkButtonHash.value(window.toInt(), nullptr);
    for (UKUITaskButton *btn : qAsConst(m_sdkButtonHash))  //delete?
        btn->setChecked(false);

    if (button)
    {
        button->setChecked(true);
        if (button->hasUrgencyHint())
            button->setUrgencyHint(false);
    }
    setChecked(nullptr != button);
}

/************************************************

 ************************************************/
void UKUITaskGroup::onDesktopChanged()
{
    refreshSdkVisibility();
    changeTaskButtonStyle();
}


void UKUITaskGroup::onWindowRemoved(const WindowId& window)
{
    qint32 windowid = window.toInt();
    m_popup->onWindowRemoved(window);
    if (m_sdkButtonHash.contains(windowid)) {
        UKUITaskButton *button = m_sdkButtonHash.value(windowid);
        m_sdkButtonHash.remove(windowid);
        if (m_sdkVisibleHash.contains(windowid)) {
            m_sdkVisibleHash.remove(windowid);
        }
        button->deleteLater();
        if (isLeaderWindow(window)) {
            setLeaderWindow(m_sdkButtonHash.begin().key());
        }
        if (m_sdkButtonHash.count()) {
            if(!m_popup->isVisible()) {
                refreshSdkVisibility();
            }
        } else {
            if (isVisible()) {
                emit visibilityChanged(false);
            }
            hide();
            emit groupBecomeEmpty(groupName());
        }
        changeTaskButtonStyle();
    }
    update();
}

void UKUITaskGroup::setToolButtonsStyle(Qt::ToolButtonStyle style)
{
    setToolButtonStyle(style);

//    const Qt::ToolButtonStyle styleInPopup = popupButtonStyle();
//    for (auto & button : m_buttonHash)
//    {
//        button->setToolButtonStyle(styleInPopup);
//    }
}

int UKUITaskGroup::visibleSdkButtonsCount() const
{
    int i = 0;
#if (QT_VERSION < QT_VERSION_CHECK(5,7,0))
    for (auto it=m_buttonHash.begin();it!=m_buttonHash.end();it++)
     {
        UKUITaskWidget *btn=it.value();
        if (btn->isVisibleTo(m_popup))
            i++;
    }
#else
    for (UKUITaskButton *btn : qAsConst(m_sdkButtonHash)) {
        if (btn->isVisibleTo(m_popup)) {
            i++;
        }
    }
#endif
    return i;
}

void UKUITaskGroup::onClicked(bool)
{
    if (1 == m_sdkVisibleHash.size()) {
        return singleSdkWindowClick();
    }
    if (m_popup->isVisible()) {
        if(HOVER == m_taskGroupStatus) {
            m_taskGroupStatus = NORMAL;
            return;
        } else {
            m_popup->hide();
            return;
        }
    } else {
        showPreviewMode();
    }
    m_taskGroupEvent = OTHEREVENT;
    if (m_timer->isActive()) {
       m_timer->stop();
    }
}

void UKUITaskGroup::singleSdkWindowClick()
{
    UKUITaskButton *btn = m_sdkVisibleHash.begin().value();
    if (btn) {
        if (!btn->isSdkFocusState() || btn->isSdkMinimized()) {
            if (m_popup->isVisible()) {
                m_popup->hide();
            }
            WindowManager::activateWindow(m_sdkVisibleHash.begin().key());
        } else {
            refreshIconsGeometry();
            btn->minimizeSdkApplication();
            if (m_popup->isVisible()) {
                m_popup->hide();
            }
        }
    }
    m_taskGroupEvent = OTHEREVENT;
    if (m_timer->isActive()) {
       m_timer->stop();
    }
}

/************************************************

 ************************************************/
void UKUITaskGroup::recalculateFrameIfVisible()
{
    if (m_popup->isVisible()) {
        recalculateFrameSize();
        if (plugin()->panel()->position() == IUKUIPanel::PositionBottom) {
            recalculateFramePosition();
        }
    }
}

void UKUITaskGroup::setAutoRotation(bool value, IUKUIPanel::Position position)
{
//    for (QWidget *button : qAsConst(m_buttonHash))
//        button->setAutoRotation(false, position);

    //UKUITaskWidget::setAutoRotation(value, position);
}

void UKUITaskGroup::refreshSdkVisibility()
{
    bool will = false;
    UKUITaskBar const * taskbar = parentTaskBar();
    const int showDesktop = taskbar->showDesktopNum();

    for(UKUISdkTaskButtonHash::const_iterator i=m_sdkButtonHash.begin(); i!=m_sdkButtonHash.end(); i++) {
        UKUITaskButton * btn=i.value();
        bool visible = btn->isOnDesktop(0 == showDesktop ? KWindowSystem::currentDesktop() : showDesktop);
        visible = true;
        btn->setVisible(visible);
        if (btn->isVisibleTo(m_popup) && !m_sdkVisibleHash.contains(i.key())) {
            m_sdkVisibleHash.insert(i.key(), i.value());
        } else if (!btn->isVisibleTo(m_popup) && m_sdkVisibleHash.contains(i.key())) {
                    m_sdkVisibleHash.remove(i.key());
        }
        will |= visible;
    }
    setLeaderWindow(m_sdkVisibleHash.begin().key());
    bool is = isVisible();
    setVisible(will);
    if (this->m_existSameQckBtn) {
        m_qckLchBtn->setHidden(will);
    }
    if (!m_popup->isVisible()) {
        recalculateFrameIfVisible();
    }

    if (is != will) {
        emit visibilityChanged(will);
    }
}

/************************************************

 ************************************************/
QMimeData * UKUITaskGroup::mimeData()
{
    QMimeData *mimedata = new QMimeData;
    QByteArray byteArray;
    QDataStream stream(&byteArray, QIODevice::WriteOnly);
    stream << groupName();
    mimedata->setData(mimeDataFormat(), byteArray);
    return mimedata;
}

/************************************************

 ************************************************/
void UKUITaskGroup::setPopupVisible(bool visible, bool fast)
{
    if (!m_statFlag) {
        return;
    }
    if (visible && !m_preventPopup) {
//        QTimer::singleShot(400, this,SLOT(showPreviewMode()));
        showPreviewMode();
        /* for origin preview
        plugin()->willShowWindow(m_popup);
        m_popup->show();
        qDebug()<<"setPopupVisible ********";
        emit popupShown(this);*/
    } else {
        m_popup->hide(fast);
    }
}

void UKUITaskGroup::refreshIconsGeometry()
{
    float scale = qApp->devicePixelRatio();
    QRect rect = geometry();
    rect.moveTo(mapToGlobal(QPoint(0, 0)).x() * scale, mapToGlobal(QPoint(0, 0)).y() * scale);
    if (m_singleButton) {
        refreshIconGeometry(rect);
        return;
    }

    for(UKUITaskButton *btn : qAsConst(m_sdkButtonHash)) {
        btn->refreshIconGeometry(rect);
    }
}

QSize UKUITaskGroup::recalculateFrameSize()
{
    int height = 120;
    m_popup->setMaximumHeight(1000);
    m_popup->setMinimumHeight(0);

    int hh = recalculateFrameWidth();
    m_popup->setMaximumWidth(hh);
    m_popup->setMinimumWidth(0);

    QSize newSize(hh, height);
    m_popup->resize(newSize);

    return newSize;
}

int UKUITaskGroup::recalculateFrameWidth() const
{
    const QFontMetrics fm = fontMetrics();
    int max = 100 * fm.width (' '); // elide after the max width
    int txtWidth = 0;
//    for (UKUITaskButton *btn : qAsConst(m_buttonHash))
//        txtWidth = qMax(fm.width(btn->text()), txtWidth);
    return iconSize().width() + qMin(txtWidth, max) + 30/* give enough room to margins and borders*/;

}

QPoint UKUITaskGroup::recalculateFramePosition()
{
    // Set position
    int x_offset = 0, y_offset = 0;
    switch (plugin()->panel()->position()) {
        case IUKUIPanel::PositionTop:
            y_offset += height();
            break;
        case IUKUIPanel::PositionBottom:
            y_offset = -120;
            break;
        case IUKUIPanel::PositionLeft:
            x_offset += width();
            break;
        case IUKUIPanel::PositionRight:
            x_offset = -recalculateFrameWidth();
            break;
    }

    QPoint pos = mapToGlobal(QPoint(x_offset, y_offset));
    m_popup->move(pos);

    return pos;
}

void UKUITaskGroup::leaveEvent(QEvent *event)
{
    //QTimer::singleShot(300, this,SLOT(mouseLeaveOut()));
    m_taskGroupEvent = LEAVEEVENT;
    if (!m_statFlag) {
        update();
        return;
    }
    m_event = event;
    if (m_timer->isActive()) {
        m_timer->stop();//stay time is no more than 400 ms need kill timer
    } else {
        if (!m_popup->geometry().contains(QCursor::pos())) {
            m_timer->start(0);
        }
    }
}

void UKUITaskGroup::enterEvent(QEvent *event)
{
    //QToolButton::enterEvent(event);
    m_taskGroupEvent = ENTEREVENT;
    if (!m_statFlag) {
        update();
        return;
    }
    m_event = event;
    m_timer->start(400);
}

void UKUITaskGroup::handleSavedEvent()
{
    if (m_draggging) {
        return;
    }
    if (!m_statFlag) {
        return;
    }

    if (m_statFlag ) {
        setPopupVisible(true);
    }
    m_taskGroupStatus = HOVER;
    repaint();
    QToolButton::enterEvent(m_event);
}

void UKUITaskGroup::dragEnterEvent(QDragEnterEvent *event)
{
    // only show the popup if we aren't dragging a taskgroup
    if (!event->mimeData()->hasFormat(mimeDataFormat())) {
        setPopupVisible(true);
    }
    UKUITaskButton::dragEnterEvent(event);
}

void UKUITaskGroup::mouseReleaseEvent(QMouseEvent *event)
{
    // only show the popup if we aren't dragging a taskgroup

    UKUITaskButton::mouseReleaseEvent(event);
}

/************************************************

 ************************************************/
void UKUITaskGroup::dragLeaveEvent(QDragLeaveEvent *event)
{
    // if draggind something into the taskgroup or the taskgroups' popup,
    // do not close the popup
    if (!m_draggging) {
        setPopupVisible(false);
    }
    UKUITaskButton::dragLeaveEvent(event);
}

void UKUITaskGroup::mouseMoveEvent(QMouseEvent* event)
{
    // if dragging the taskgroup, do not show the popup
    setPopupVisible(false, true);
    UKUITaskButton::mouseMoveEvent(event);
}
/************************************************

 ************************************************/
bool UKUITaskGroup::onWindowChanged(const WindowId& window)
{ // returns true if the class is preserved
    bool needsRefreshVisibility{false};
    QVector<QWidget *> buttons;
    if (m_sdkButtonHash.contains(window.toInt())) {
        buttons.append(m_sdkButtonHash.value(window.toInt()));
    }

    // If group is based on that window properties must be changed also on button group
    if (window == sdkWindowId()) {
        buttons.append(this);
    }

    if (!buttons.isEmpty()) {
        // if class is changed the window won't belong to our group any more
//        if (parentTaskBar()->isGroupingEnabled() /*&& prop2.testFlag(NET::WM2WindowClass)*/) {
//            QString groupName = WindowManager::getWindowGroup(window);
//            if (groupName != m_groupName) {
//                onWindowRemoved(window);
//                return false;
//            }
//        }

        if (!WindowManager::getWindowIcon(window).isNull()){
            updateSdkIcon();
            for(UKUISdkTaskButtonHash::const_iterator i = m_sdkVisibleHash.begin(); i != m_sdkVisibleHash.end(); i++) {
                i.value()->updateSdkIcon();
            }
            m_popup->onWindowChanged(window);
        }
    }
    if (needsRefreshVisibility) {
        refreshSdkVisibility();
    }
    if (!m_isShowByList) {
        m_allWidgetWidth = m_popup->width();
        m_allWidgetHeight = m_popup->height();
    }

    return true;
}

void UKUITaskGroup::timeout()
{
    if (m_taskGroupEvent == ENTEREVENT) {
        if (m_timer->isActive()) {
            m_timer->stop();
        }
        handleSavedEvent();
    } else if(m_taskGroupEvent == LEAVEEVENT) {
        if (m_timer->isActive()) {
            m_timer->stop();
        }
        setPopupVisible(false);
        QToolButton::leaveEvent(m_event);
        m_taskGroupStatus = NORMAL;
        update();
    } else {
        setPopupVisible(false);
    }
}

void UKUITaskGroup::showPreviewMode()
{
    m_popup->showPreviewMode();

}

void UKUITaskGroup::paintEvent(QPaintEvent *event)
{
    QToolButton::paintEvent(event);

    QStyleOption option;
    option.initFrom(this);
    QPainter painter(this);
    if(m_sdkVisibleHash.size() > 1) {
        painter.setRenderHint(QPainter::Antialiasing, true);
        painter.setPen(QPen(option.palette.color(QPalette::Highlight), 4, Qt::SolidLine, Qt::RoundCap));
        painter.drawLine(QPoint(option.rect.center().x() - 6, option.rect.bottomLeft().y() - 3),
                         QPoint(option.rect.center().x() + 6, option.rect.bottomLeft().y() - 3));
    } else if(m_sdkVisibleHash.size() == 1) {
        painter.setRenderHint(QPainter::Antialiasing, true);
        painter.setPen(QPen(option.palette.color(QPalette::Highlight), 4, Qt::SolidLine, Qt::RoundCap));
        painter.drawLine(QPoint(option.rect.center().x() - 2, option.rect.bottomLeft().y() - 3),
                         QPoint(option.rect.center().x() + 2, option.rect.bottomLeft().y() - 3));
    }
    return;
}
